#include "stdafx.h"
#include "core/logger.h"

namespace natrium {
	namespace core {
		Logger::Logger(const std::string& filename):
			mOutput(filename),
			mDone(false)
		{
			mThread.emplace_back([this] { while (!mDone) mBacklog.pop()(); });
		}

		Logger::~Logger() {
			mBacklog.push([this] { mDone = true; });
			mThread[0].join();
		}

		Logger::LogHelper Logger::operator << (std::ostream& (*fn)(std::ostream& os)) {
			LogHelper lh(this);
			fn(lh.mBuffer);
			return lh;
		}

		//helper function to convert a time point to a regular string
		std::string asString(const std::chrono::system_clock::time_point& tp) {
			auto timeStruct = std::chrono::system_clock::to_time_t(tp);

	#pragma warning(push)
	#pragma warning(disable: 4996)
			std::string result = std::ctime(&timeStruct);
	#pragma warning(pop)
			
			result.resize(result.size() - 1); //get rid of the trailing newline
			return result;
		}

		Logger::LogHelper::LogHelper(Logger* owner):
			mOwner(owner)
		{
			mBuffer << "[" << asString(std::chrono::system_clock::now()) << "] "; //always add a timestamp in front of the message
		}

		Logger::LogHelper::LogHelper(LogHelper&& lh):
			mOwner(std::move(lh.mOwner)),
			mBuffer(std::move(lh.mBuffer))
		{
			lh.mOwner = nullptr;
		}

		Logger::LogHelper::~LogHelper() {
			if (mOwner == nullptr)
				return; //the helper has been moved; skip flushing

			mBuffer << std::endl; //automatically add a newline
			mOwner->flush(mBuffer.str());
		}

		Logger::LogHelper& Logger::LogHelper::operator << (std::ostream& (*fn)(std::ostream& os)) {
			fn(mBuffer);
			return *this;
		}

		void Logger::flush(const std::string& message) {
			/*
			mOutput.mOutputFile << message;
			
			#ifdef _DEBUG
				mOutput.mCout << message;
			#endif
			*/
			mBacklog.push([=] {
				mOutput.mOutputFile << message;
				
				#ifdef _DEBUG
					mOutput.mCout << message;
				#endif
			});
		}

		Logger::LogHelper Logger::debug() {
			LogHelper lh(this);
			lh << "* DEBUG * ";
			return lh;
		}
	
		Logger::LogHelper Logger::info() {
			LogHelper lh(this);
			lh << "* INFO * ";
			return lh;
		}
	
		Logger::LogHelper Logger::warning() {
			LogHelper lh(this);
			lh << "* WARNING * ";
			return lh;
		}
	
		Logger::LogHelper Logger::error() {
			LogHelper lh(this);
			lh << "* ERROR * ";
			return lh;
		}
	
		Logger::LogHelper Logger::fatal() {
			LogHelper lh(this);
			lh << "* FATAL * ";
			return lh;
		}

		Logger::Output::Output(const std::string& filename):
			mOutputFile(filename),
			mCout(std::cout)
		{
		}
	}
}

natrium::core::Logger gLog("oa.log");
